﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using XMLGener.Utils;

namespace XMLGener.Data
{
    /// <summary>
    /// 
    /// </summary>
    public class XMLGenerElement:XMLGenerRangeElement
    {
        #region constructors
        /// <summary>
        /// 
        /// </summary>
        public static string TYPE_ONLY_ELEMENTS = "OnlyElements";
        /// <summary>
        /// 
        /// </summary>
        protected XMLGenerElement()
            : base()
        {
            this.xtype = XNodeType.ELEMENT;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="parent"></param>
        public XMLGenerElement(String name, XMLGenerNode parent)
            : base(name, parent)
        {
            this.xtype = XNodeType.ELEMENT;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="xmlElem"></param>
        public XMLGenerElement(XMLGenerNode parent, XmlElement xmlElem)
            : base(parent,xmlElem)
        {
            this.xtype = XNodeType.ELEMENT;
            
            //this.name = xmlElem.GetAttribute("name");

        }

        /// <summary>
        /// 
        /// </summary>
        protected override void defaultInitialization()
        {
            base.defaultInitialization();
            this.nodeTypeStr = XMLGenerNode.XELEMENT_STR;
            this.attributes = new List<XMLGenerAttribute>();
        }
        #endregion

        #region childNodes and attributes
        /// <summary>
        /// 
        /// </summary>
        public List<XMLGenerAttribute> attributes = null;

        /// <summary>
        /// List all child nodes including attributes
        /// </summary>
        public override List<XMLGenerNode> allChildNodes
        {
            get 
            {
                List<XMLGenerNode> allNodes = new List<XMLGenerNode>();
                if (this.baseType != null || this.contentType != TYPE_ONLY_ELEMENTS)
                    return allNodes;
                allNodes.AddRange(this.childNodes);
                allNodes.AddRange(this.attributes.ToArray());
                return allNodes;
            }
        }


        /// <summary>
        /// Add node to childNodes and set self as parent node.
        /// </summary>
        /// <param name="node">Node to add to childNodes</param>
        public override void addChildNode(XMLGenerNode node)
        {
            if (!(node is XMLGenerAttribute))
            {
                base.addChildNode(node);
                return;
            }
            XMLGenerAttribute xattr = (XMLGenerAttribute) node;
            if (this.attributes.Contains(xattr))
                return;
            this.attributes.Add(xattr);
            if (node.parentNode != this)
                node.parentNode = this;
        }

        /// <summary>
        /// Add attribubute to childNodes and set self as parent node.
        /// </summary>
        /// <param name="attribubute">Node to add to childNodes</param>
        public virtual void addAttributeNode(XMLGenerAttribute attribubute)
        {
            if (this.attributes.Contains(attribubute))
                return;
            this.attributes.Add(attribubute);
            if (attribubute.parentNode != this)
                attribubute.parentNode = this;
        }
        #endregion
        /// <summary>
        /// Return nodes type.
        /// </summary>
        public override string contentType
        {
            get {
                if (this.baseType != null)
                    return baseType.contentType;
                return getXMLParam("type", TYPE_ONLY_ELEMENTS); 
            }
            set { setXMLParam("type", value); }

        }

        private XMLGenerElement _baseType = null;
        private bool baseTypeIsLoaded = false;
        
        public XMLGenerElement baseType 
        {
            get{
                if (baseTypeIsLoaded)
                    return _baseType;
                if (this.ownerDocument != null)
                    _baseType = this.ownerDocument.getElementType(parentTypeName);
                else
                    _baseType = null;
                baseTypeIsLoaded = true;
                return _baseType;
            }
        }
        
      


        /// <summary>
        /// Name of parent type of this data type. Default type is "any"
        /// </summary>
        public override string parentTypeName
        {
            get { return getXMLParam("parentType", ""); }
            set 
            { 
                setXMLParam("parentType", value);
                this.baseTypeIsLoaded = false;
                loadBaseType();
            }
        }
        private void loadBaseType()
        {
            if (this.baseType == null)
                return;
            this.minCount = baseType.minCount;
            this.maxCount = baseType.maxCount;
            this.minFrequency = baseType.minFrequency;
            this.maxFrequency = baseType.maxFrequency;
            this.minFanout = baseType.minFanout;
            this.maxFanout = baseType.maxFanout;
            this.minDepth = baseType.minDepth;
            this.maxDepth = baseType.maxDepth;
          
            this.useFrequency = baseType.useFrequency;
            this.useFanout = baseType.useFanout;
            this.useDepth = baseType.useDepth;
        }

        /// <summary>
        /// Return avialable types for this type of node
        /// </summary>
        /// <returns>list of strings</returns>
        public override XDataTypeCollection listAvailabeTypes()
        {
            XDataTypeCollection result = new XDataTypeCollection();
            if (this.baseType != null)
                return result;

            if (this.ownerDocument != null)
                result.AddRange( this.ownerDocument.getAllDataTypes());
            else
                result.AddRange(XMLGenerDataTypes.getDefaultTypes());
            result.Insert(0, baseTypesList[0]);
            return result;
          
        }

        private static XDataTypeCollection _baseTypesList = null;

        private static XDataTypeCollection baseTypesList
        {
            get
            {
                if (_baseTypesList == null)
                    loadElementsTypes();
                return _baseTypesList;
            }
        }

        private static XmlDocument typesDefinition = null;
        private static void loadElementsTypes()
        {
            typesDefinition = new XmlDocument();
            typesDefinition.LoadXml(
                "<doc><" + XMLGenerNode.XDATATYPE_STR + " name=\""+TYPE_ONLY_ELEMENTS+"\"/>" +
                 "</doc>"
                );
            XmlElement node = XMLUtils.getSubElement(typesDefinition.DocumentElement, XMLGenerNode.XDATATYPE_STR);
            XDataTypeCollection list = new XDataTypeCollection();
            while (node != null)
            {
                XDataType newType = new XDataType(node);
                newType.isRootType = true;
                list.Add(newType);
                node = XMLUtils.getNextElement(node, XMLGenerNode.XDATATYPE_STR);
            }
            _baseTypesList = list;

        }
        
            
        /// <summary>
        /// Return all elements definition which this element can 
        /// be intherited from
        /// </summary>
        /// <returns>Collection of node types</returns>
        public override XDataTypeCollection listAvailabeParents()
        {
            if (this.ownerDocument != null)
                return this.ownerDocument.getAllParentTypes(this);
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="child"></param>
        protected override void removeChildNode(XMLGenerNode child)
        {
            if (!(child is XMLGenerAttribute))
            {
                base.removeChildNode(child);
                return;
            }
            XMLGenerAttribute remAttr = (XMLGenerAttribute)child;
            if (this.attributes.IndexOf(remAttr) < 0)
                return;
            this.attributes.Remove(remAttr);
            XmlElement attributesXml = XMLUtils.getSubElement(this.xmlElem, XATTRIBUTES_STR);
            if(attributesXml!=null)
                attributesXml.RemoveChild(remAttr.xmlElem);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="child">child node</param>
        /// <param name="count">direction and distance to move ( -1 or 1)</param>
        /// <returns>True if node's position was changed</returns>
        protected override bool changeChildPosition(XMLGenerNode child, int count)
        {
            if (!(child is XMLGenerAttribute))
            {
                return base.changeChildPosition(child, count);
            }
            // move attribute
            XMLGenerAttribute attr = (XMLGenerAttribute)child;
            int pos = this.attributes.IndexOf(attr);
            // check position boundaries
            int i = pos;
            if (i < 0) return false;
            i += count;
            if (i < 0) i = 0;
            if (i >= this.attributes.Count) i = this.attributes.Count - 1;
            XmlElement xattr = XMLUtils.getSubElement(this.xmlElem,XATTRIBUTES_STR);
            if (xattr == null)
                return false;

            // try to swap position of XML node
            if (count > 0)
            {
                // move element down
                XmlElement nextElem = XMLUtils.getNextElement(child.xmlElem);
                if (nextElem == null || nextElem.Name == XATTRIBUTES_STR)
                {
                    // there is no option to move
                    return false;
                }
                xattr.RemoveChild(child.xmlElem);
                xattr.InsertAfter(child.xmlElem, nextElem);
            }
            else
            {
                // move element up
                XmlElement prevElem = XMLUtils.getPrevoiusElement(child.xmlElem);
                if (prevElem == null)
                {
                    // there is no option to move
                    return false;
                }
                xattr.RemoveChild(child.xmlElem);
                xattr.InsertBefore(child.xmlElem, prevElem);
            }

            // set position of XGenerNode
            this.attributes.RemoveAt(pos);
            this.attributes.Insert(i, attr);

            return true;
        }



        public override XMLGenerNode getCopy(int depth)
        {
          
            XMLGenerElement result = new XMLGenerElement(null, this.xmlElem);
            result.xmlElem = this.xmlElem;
            result.originalNode = this;
            result._isCopy = true;
            if (depth > 0)
            {
                int newDepth = depth - 1;
                foreach (XMLGenerNode child in this.childNodes)
                {
                    result.addChildNode(child.getCopy(0));
                }
            }

            if (depth > 0)
            {
                foreach (XMLGenerNode attr in this.attributes)
                    result.addChildNode(attr.getCopy(0));
            }

            return result;
        }
        //---------------------------
        // attributes from xml
        //---------------------------
        
        /// <summary>
        /// 
        /// </summary>
        public bool isMainElement = false;

        //---------------------------
        // generating 
        //---------------------------

        /// <summary>
        /// 
        /// </summary>
        public override bool initModule()
        {
            if (this.contentType == XMLGenerElement.TYPE_ONLY_ELEMENTS)
                return true;
            if (modInst != null)
                return true;

            if (this.module == null)
            {
                if (parentNode!=null)
                    logger.addLog(this.GetType().ToString(), "Element " + this.name + " has not defined generator module.");
                else
                    logger.addLog(this.GetType().ToString(), "Main element " + this.name + " has not defined generator module.");
                return true;
            }
            this.modInst = XModuleManager.getModuleInstance(this.module);
            if (this.modInst == null)
                return false;
            if (settingsElement != null)
            {
                try
                {
                    this.modInst.setParameters(getSettingsForModule(), ownerDocument);
                }
                catch (Exception e)
                {
                    logger.addErrorLog(this.GetType().ToString(), "Error:" +e.Message );
                }
            }
            return true;
        }

        private string getSettingsForModule()
        {
            string result = "";
            if (settingsElement != null)
                result = settingsElement.OuterXml;
            if (this.ownerDocument != null)
                result = result + this.ownerDocument.getRestictionForDataType(this.contentType);


            return "<modulesettings>" + result + "</modulesettings>";
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string getGeneratedString()
        {
            if (this.modInst == null)
                return null;
            return modInst.generate();
        }






        

    }
}
